﻿
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;

using Dlr = Microsoft.Scripting;

namespace IronLua.Ast {

	public class LuaPrinter : BaseAstVisitor {

		public static void PrintCode (TextWriter writer, LuaNode node)
		{
			var printer = new LuaPrinter (writer);
			node.Accept (printer);
		}

		public static string ToCodeString (LuaNode node)
		{
			var writer = new StringWriter ();
			PrintCode (writer, node);
			return writer.ToString ();
		}

		const string ListSeparator = ", ";

		TextWriter writer;
		bool write_indent;
		int indent;

		private LuaPrinter (TextWriter writer)
		{
			this.writer = writer;
		}

		void WriteIndent ()
		{
			if (!write_indent)
				return;

			for (int i = 0; i < indent; i++)
				writer.Write ("\t");
		}

		void Write (object o)
		{
			WriteIndent ();
			writer.Write (o);
			write_indent = false;
		}

		void Write (string s)
		{
			WriteIndent ();
			writer.Write (s);
			write_indent = false;
		}

		void Write (string s, params object [] objs)
		{
			WriteIndent ();
			writer.Write (s, objs);
			write_indent = false;
		}

		void WriteLine ()
		{
			writer.WriteLine ();
			write_indent = true;
		}

		void WriteLine (string s)
		{
			WriteIndent ();
			writer.WriteLine (s);
			write_indent = true;
		}

		void WriteLine (string s, params object [] objs)
		{
			WriteIndent ();
			writer.WriteLine (s, objs);
			write_indent = true;
		}

		void Indent ()
		{
			indent++;
			write_indent = true;
		}

		void Dedent ()
		{
			indent--;
		}

		void WriteIndented (Action action)
		{
			Indent ();
			action ();
			Dedent ();
		}

		void WriteDoBlock (Action action)
		{
			WriteLine ("do");
			WriteIndented (action);
			WriteLine ("end");
		}

		void WriteBetweenSpaces (Action action)
		{
			Write (" ");
			action ();
			Write (" ");
		}

		void WriteBetweenSpaces (string s)
		{
			Write (" ");
			Write (s);
			Write (" ");
		}

		void WriteBetweenParenthesis (Action action)
		{
			Write ("(");
			action ();
			Write (")");
		}

		void WriteBetweenBrackets (Action action)
		{
			Write ("[");
			action ();
			Write ("]");
		}

		void WriteSpace ()
		{
			Write (" ");
		}

		void VisitList<TElement> (IEnumerable<TElement> list, string separator) where TElement : LuaNode
		{
			int i = 0;
			foreach (var e in list) {
				if (i > 0)
					Write (separator);

				e.Accept (this);
				i++;
			}
		}

		public override void VisitList<TElement> (IEnumerable<TElement> list)
		{
			VisitList (list, ListSeparator);
		}

		public override void VisitBlockStatement (BlockStatement node)
		{
			VisitList (node.Statements, string.Empty);
		}

		public override void VisitAssignStatement (AssignStatement node)
		{
			VisitList (node.Left);
			WriteBetweenSpaces ("=");
			VisitList (node.Right);
			WriteLine ();
		}

		public override void VisitDoStatement (DoStatement node)
		{
			WriteDoBlock (() => node.Body.Accept (this));
		}

		public override void VisitWhileStatement (WhileStatement node)
		{
			Write ("while");
			WriteBetweenSpaces (() => node.Condition.Accept (this));
			WriteDoBlock (() => node.Body.Accept (this));
		}

		public override void VisitRepeatStatement (RepeatStatement node)
		{
			WriteLine ("repeat");
			WriteIndented (() => node.Body.Accept (this));
			Write ("until");
			WriteSpace ();
			node.Until.Accept (this);
			WriteLine ();
		}

		public override void VisitIfStatement (IfStatement node)
		{
			Write ("if");
			WriteBetweenSpaces (() => node.Condition.Accept (this));
			WriteLine ("then");
			WriteIndented (() => node.Then.Accept (this));

			VisitList (node.ElseIfs, string.Empty);

			if (node.Else != null) {
				WriteLine ("else");
				WriteIndented (() => node.Else.Accept (this));
			}

			WriteLine ("end");
		}

		public override void VisitElseIfClause (ElseIfClause node)
		{
			Write ("elseif");
			WriteBetweenSpaces (() => node.Condition.Accept (this));
			WriteLine ("then");
			WriteIndented (() => node.Then.Accept (this));
		}

		public override void VisitReturnStatement (ReturnStatement node)
		{
			Write ("return");
			WriteSpace ();
			VisitList (node.Expressions);
			WriteLine ();
		}

		public override void VisitBreakStatement (BreakStatement node)
		{
			WriteLine ("break");
		}

		public override void VisitExpressionStatement (ExpressionStatement node)
		{
			node.Expression.Accept (this);
			WriteLine ();
		}

		public override void VisitCommentStatement (CommentStatement node)
		{
			Write ("--");
			WriteSpace ();
			WriteLine (node.Comment);
		}

		public override void VisitForStatement (ForStatement node)
		{
			Write ("for");
			WriteBetweenSpaces (() => node.Variable.Accept (this));
			Write ("=");
			WriteSpace ();
			node.Initializer.Accept (this);
			Write (ListSeparator);
			node.Limit.Accept (this);

			if (node.Step != null) {
				Write (ListSeparator);
				node.Step.Accept (this);
			}

			WriteSpace ();
			WriteDoBlock (() => node.Body.Accept (this));
		}

		public override void VisitForEachStatement (ForEachStatement node)
		{
			Write ("for");
			WriteBetweenSpaces (() => VisitList (node.Variables));
			Write ("in");
			WriteBetweenSpaces (() => VisitList (node.Expressions));
			WriteDoBlock (() => node.Body.Accept (this));
		}

		public override void VisitLocalStatement (LocalStatement node)
		{
			Write ("local");
			WriteBetweenSpaces (() => VisitList (node.Variables));
			Write ("=");
			WriteSpace ();
			VisitList (node.Expressions);
			WriteLine ();
		}

		static string OperatorToString (UnaryOperatorType op)
		{
			switch (op) {
			case UnaryOperatorType.Length:
				return "#";
			case UnaryOperatorType.Negation:
				return "-";
			case UnaryOperatorType.Not:
				return "not";
			default:
				throw new NotSupportedException ();
			}
		}

		public override void VisitUnaryExpression (UnaryExpression node)
		{
			Write (OperatorToString (node.Operator));
			WriteBetweenParenthesis (() => node.Expression.Accept (this));
		}

		static string OperatorToString (BinaryOperatorType op)
		{
			switch (op) {
			case BinaryOperatorType.Add:
				return "+";
			case BinaryOperatorType.And:
				return "and";
			case BinaryOperatorType.Divide:
				return "/";
			case BinaryOperatorType.Equal:
				return "==";
			case BinaryOperatorType.GreaterThan:
				return ">";
			case BinaryOperatorType.GreaterThanOrEqual:
				return ">=";
			case BinaryOperatorType.LessThan:
				return "<";
			case BinaryOperatorType.LessThanOrEqual:
				return "<=";
			case BinaryOperatorType.Modulo:
				return "%";
			case BinaryOperatorType.Multiply:
				return "*";
			case BinaryOperatorType.NotEqual:
				return "~=";
			case BinaryOperatorType.Or:
				return "or";
			case BinaryOperatorType.Power:
				return "^";
			case BinaryOperatorType.Subtract:
				return "-";
			case BinaryOperatorType.Concat:
				return "..";
			default:
				throw new InvalidOperationException ();
			}
		}

		public override void VisitBinaryExpression (BinaryExpression node)
		{
			WriteBetweenParenthesis (() => {
				node.Left.Accept (this);
				WriteBetweenSpaces (OperatorToString (node.Operator));
				node.Right.Accept (this);
			});
		}

		public override void VisitLiteralExpression (LiteralExpression node)
		{
			Write (FormatLiteral (node.Value));
		}

		string FormatLiteral (object obj)
		{
			if (obj == null)
				return "nil";
			if (obj == Dlr.Runtime.RuntimeHelpers.True)
				return "true";
			if (obj == Dlr.Runtime.RuntimeHelpers.False)
				return "false";
			if (obj is string)
				return "\"" + obj + "\"";
			if (obj is double)
				return ((double) obj).ToString (NumberFormatInfo.InvariantInfo);

			return obj.ToString ();
		}

		public override void VisitTableCreationExpression (TableCreationExpression node)
		{
			Write ("{}");
		}

		public override void VisitVariableExpression (VariableExpression node)
		{
			Write (node.Name);
		}

		public override void VisitSlotExpression (SlotExpression node)
		{
			node.Target.Accept (this);
			WriteSpace ();
			WriteBetweenBrackets (() => node.Expression.Accept (this));
		}

		public override void VisitMemberExpression (MemberExpression node)
		{
			node.Target.Accept (this);
			Write (".");
			Write (node.Name);
		}

		public override void VisitFunctionCallExpression (FunctionCallExpression node)
		{
			node.Target.Accept (this);
			WriteSpace ();
			WriteBetweenParenthesis (() => VisitList (node.Arguments));
		}

		public override void VisitMethodCallExpression (MethodCallExpression node)
		{
			node.Target.Accept (this);
			Write (":");
			Write (node.Name);
			WriteSpace ();
			WriteBetweenParenthesis (() => VisitList (node.Arguments));
		}

		public override void VisitFunctionExpression (FunctionExpression node)
		{
			Write ("function");
			WriteSpace ();
			WriteBetweenParenthesis (() => VisitList (node.Parameters));
			WriteLine ();
			WriteIndented (() => node.Body.Accept (this));
			Write ("end");
		}

		public override void VisitVarArgExpression (VarArgExpression node)
		{
			Write ("...");
		}
	}
}
